<?php

namespace PHPSpring\Web;

// TODO: 替换成PDO
use Illuminate\Support\Facades\DB;
use PHPSpring\Utils\Utils;
use ReflectionClass;
use ReflectionProperty;

abstract class BaseDao {

  protected string $entityClass;
  protected string $pk;
  protected int $idType;
  protected string $table;
  protected array $fields;

  public function insert ($entity) {
    $object = $this->entityToArray($entity);
    $method = $this->idType == \PHPSpring\Type\IDType::AUTO ? 'insertGetId' : 'insert';
    return DB::table($this->table)->$method($object);
  }

  public function insertBatch (array $entites) {
    $objects = array();
    foreach ($entites as $entity) {
      $objects[] = $this->entityToArray($entity);
    }
    return DB::table($this->table)->insert($objects);
  }

  public function delete ($condition) {
    $where = $this->conditionToWhere($condition);
    return DB::table($this->table)->where($where)->delete();
  }

  public function deleteById ($id) {
    return DB::table($this->table)->where(Utils::varToField($this->pk), $id)->delete();
  }

  public function deleteBatchIds (array $ids) {
    if (empty($ids)) {
      return null;
    }
    return DB::table($this->table)->whereIn(Utils::varToField($this->pk), $ids)->delete();
  }

  public function update ($entity, $condition) {
    $where = array();
    $pkField = Utils::varToField($this->pk);
    $record = $this->entityToArray($entity);    
    if (empty($condition)) {
      if (!isset($record[$pkField])) {
        return 0;
      }
      $where[$pkField] = $record[$pkField];
    } else {
      $where = $this->conditionToWhere($condition);
    }
    
    if (isset($record[$pkField])) {
      unset($record[$pkField]);
    }
    return DB::table($this->table)->where($where)->update($record);
  }

  public function updateById ($entity) {
    return $this->update($entity, null);
  }

  public function selectById($id) {
    return $this->selectOne(array($this->pk => $id), null);
  }

  public function selectOne ($condition, $orderBy=null) {
    $where = $this->conditionToWhere($condition);
    $chain = $this->makeOrderBy(DB::table($this->table)->where($where), $orderBy);
    $record = $chain->first();
    return $this->arrayToEntity($record);
  }

  public function selectBatchIds ($ids, $orderBy=null) {
    if (empty($ids)) {
      return null;
    }
    $chain = $this->makeOrderBy(DB::table($this->table)->whereIn(Utils::varToField($this->pk), $ids), $orderBy);
    $records = $chain->get();
    return $this->listToEntites($records);
  }

  public function selectList ($condition, $orderBy=null) {
    $where = $this->conditionToWhere($condition);
    $chain = $this->makeOrderBy(DB::table($this->table)->where($where), $orderBy);
    $records = $chain->get();
    return $this->listToEntites($records);
  }

  public function selectPage ($condition, $offset, $limit, $orderBy=null) {
    $count = $this->selectCount($condition);
    $where = $this->conditionToWhere($condition);
    $chain = $this->makeOrderBy(DB::table($this->table)->where($where), $orderBy);
    $records = $this->listToEntites($chain->offset($offset)->limit($limit)->get());
    return array($count, $records);
  }

  public function selectCount ($condition=null) {
    $where = $this->conditionToWhere($condition);
    return DB::table($this->table)->where($where)->count();
  }

  public function query ($sql, $params=null, $entityClz=null) {
    // $pdo = DB::connection()->getPdo();
    // $sth = $pdo->prepare('select * from user');
    // $sth->execute();
    // $us = $sth->fetchAll();
    // var_dump($us);
    if (empty($params)) {
      $params = array();
    }
    $toEntity = false;
    $type = strtolower(substr(trim($sql), 0, 3));
    $records = array();
    $method = 'statement';
    if ($type == 'sel') {
      $method = 'select';
      $toEntity = true;
    } else if ($type == 'ins') {
      $method = 'insert';
    } else if ($type == 'upd') {
      $method = 'update';
    } else if ($type == 'del') {
      $method = 'delete';
    }
    $records = DB::$method($sql, $params);
    if ($toEntity) {
      $records = $this->listToEntites($records, $entityClz);
    }
    return $records;
  }

  /**
   * 组装orderBy，entity字段名自动转成表字段名
   * 
   * @param $chain: Laravel DB call chain
   * @param $orderBy: array('id', 'name'=>'desc', 'create_time')
   * @return $chain
   */
  protected function makeOrderBy ($chain, $orderBy) {
    if (empty($orderBy)) {
      return $chain;
    }
    foreach ($orderBy as $field => $dir) {
      if (is_numeric($field)) {
        $chain = $chain->orderBy(Utils::varToField($dir), 'asc');
      } else {
        $chain = $chain->orderBy(Utils::varToField($field), $dir);
      }
    }
    return $chain;
  }

  /**
   * 组装查询条件，entity字段名自动转成表字段名
   *
   * @param $condition = array('uid' => 1, 'name' => array('like', '%zh%'), 'age' => array('<>', 3))
   * @return array
   */
  private function conditionToWhere ($condition) {
    $where = array();
    if (!empty($condition)) {
      foreach ($condition as $k => $v) {
        if ($v === null) {
          continue;
        }
        $k0 = Utils::varToField($k);
        if (is_array($v)) {
          if (count($v) == 2) {
            $where[] = array($k0, $v[0], $v[1]);
          } else {
            continue;
          }
        } else {
          $where[] = array($k0, $v);
        }
      }
    }
    return $where;
  }

  /**
   * entity对象按表字段名转成数组，用于sql执行
   *
   * @param $entity
   * @return array $arr
   */
  protected final function entityToArray ($entity): array {
    $arr = array();
    $re = null;
    $publicInitedProperties = array();
    foreach ($entity as $k => $v) {
      $publicInitedProperties[$k] = true;
    }
    foreach ($this->fields as $k => $info) {
      if (is_array($entity)) {
        if (!isset($entity[$k])) {
          continue;
        }
        $v = $entity[$k];
      } else {
        list ($type, $isPublic) = $info;
        if ($isPublic) {
          if (!isset($publicInitedProperties[$k])) {
            continue;
          }
          $v = $entity->$k;
        } else {
          if (!$re) {
            $re = new ReflectionClass($this->entityClass);
          }
          $p = $re->getProperty($k);
          $p->setAccessible(true);
          if (!$p->isInitialized($entity)) {
            continue;
          }
          $v = $p->getValue($entity);
        }
      }
      if ($v !== null) {
        $k0 = Utils::varToField($k);
        $arr[$k0] = $v;
      }
    }
    return $arr;
  }

  /**
   * 单条表记录转成entity对象，用于返回结果的处理
   *
   * @param mixed $array
   * @param string|null $entityClz
   * @return object|null $entity
   * @throws \ReflectionException
   */
  protected final function arrayToEntity($array, string $entityClz = null)
  {
    if (empty($array) || empty($this->entityClass)) {
      return null;
    }
    if (!$entityClz) {
      $entityClz = $this->entityClass;
    }
    $entity = new $entityClz;
    foreach ($array as $k => $v) {
      $property = Utils::fieldToVar($k);
      $ref = new ReflectionProperty($entityClz, $property);
      if (!$ref->isPublic()) {
        continue;
      }

      $refType = $ref->getType();
      $type = null;
      if ($refType) {
        $type = $refType->getName();
        if ($type && !in_array($type, ['null', 'int', 'float', 'double', 'string', 'bool', 'array', 'mixed', 'object']) && class_exists($type)) {
          $v = new $type($v);
        }
      }

      $entity->$property = $v;
    }

    return $entity;
  }

  /**
   * 多条表记录转成entity对象，用于返回结果的处理
   *
   * @param $list
   * @return $entities
   */
  protected final function listToEntites ($list, $entityClz=null) {
    if (empty($list)) {
      return null;
    }
    $ret = array();
    foreach ($list as $arr) {
      $ret[] = $this->arrayToEntity($arr, $entityClz);
    }
    return $ret;
  }

}
